package com.sunlands.qdcy.deskmate.service;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.domain.AlipayTradeQueryModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.request.AlipayTradeQueryRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.alipay.api.response.AlipayTradeQueryResponse;
import com.sunlands.qdcy.deskmate.config.AlipayConfig;
import com.sunlands.qdcy.deskmate.dao.pojo.OrderRecordDO;
import com.sunlands.qdcy.deskmate.dao.pojo.PaymentAliPayNotifyRecordDO;
import com.sunlands.qdcy.deskmate.dao.repository.PaymentAliPayNotifyRecordRepository;
import com.sunlands.qdcy.deskmate.enums.AlipayTradeStatus;
import com.sunlands.qdcy.deskmate.enums.OrderStatus;
import com.sunlands.qdcy.deskmate.enums.PayType;
import com.sunlands.qdcy.deskmate.enums.YN;
import com.sunlands.qdcy.deskmate.exception.BusinessException;
import com.sunlands.qdcy.deskmate.util.DateUtil;
import com.sunlands.qdcy.deskmate.util.OrderSnUtil;
import com.sunlands.qdcy.deskmate.vo.Order;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.*;

import static com.sunlands.qdcy.deskmate.util.DateUtil.SHORT_DATA_FORMAT_YYYY_MMDD_HHMM;

/**
 * @author anjl
 * Date: 2019-04-24
 * Description:
 */
@Slf4j
@Service
public class AlipayService {

    public static final String ALIPAY_SUCCESS = "10000";
    public static final String ALIPAY_NO_EXIST = "40004";

    @Autowired
    private OrderRecordService orderRecordService;
    @Autowired
    private AlipayClient alipayClient;
    @Autowired
    private AlipayConfig alipayConfig;
    @Autowired
    private PaymentAliPayNotifyRecordRepository paymentAliPayNotifyRecordRepository;
    /**
     * 支付宝下单接口
     *
     * @param order 商品标题
     * @return
     * @throws AlipayApiException
     */
    public AlipayTradeAppPayResponse createOrder(Order order) throws AlipayApiException {
        if (Objects.isNull(order.getTotalAmount()) || order.getTotalAmount().compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException("付款金额不正确！");
        }
        if (StringUtils.isBlank(order.getSubject())) {
            throw new NullPointerException("subject 商品标题不能为空");
        }
        //生成订单号-uuid
        String outTradeNo = OrderSnUtil.getOrderSn(PayType.ALIPAY.getType());
        order.setOutTradeNo(outTradeNo);

        AlipayTradeAppPayRequest alipayRequest = new AlipayTradeAppPayRequest();//创建API对应的request
        AlipayTradeAppPayModel model = buildAliPayModel(order);
        alipayRequest.setBizModel(model);
        alipayRequest.setNotifyUrl(alipayConfig.getNotifyUrl());
        AlipayTradeAppPayResponse response = alipayClient.sdkExecute(alipayRequest);
        log.info("支付宝下单-接口返回 response = {}", JSONObject.toJSONString(response));
        response.setOutTradeNo(outTradeNo);

        // 订单信息入库
        OrderRecordDO from = from(order);
        from.setTradeNo(response.getTradeNo());
        OrderRecordDO record = orderRecordService.save(from);
        log.info("下单成功OrderRecordDO = {}", record);

        return response;

    }



    private AlipayTradeAppPayModel buildAliPayModel(Order order) {
        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        model.setBody(order.getBody());
        model.setSubject(order.getSubject());
        model.setOutTradeNo(order.getOutTradeNo());
        //支付宝订单有效时间为30分钟
        model.setTimeExpire(getAliPayOrderTimeExpire(60));
        model.setTotalAmount(String.valueOf(order.getTotalAmount()));
        model.setProductCode("QUICK_MSECURITY_PAY");    //固定值
        model.setGoodsType("0");    //虚拟商品
        return model;
    }

    /**
     * 获取支付宝订单超时时间，单位分钟
     *
     * @return
     */
    public static String getAliPayOrderTimeExpire(int minute) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTime(new Date());
        calendar.set(Calendar.MINUTE,
                calendar.get(Calendar.MINUTE) + minute);
        return DateUtil.format(calendar.getTime(), SHORT_DATA_FORMAT_YYYY_MMDD_HHMM);
    }


    private OrderRecordDO from(Order order) {
        return OrderRecordDO.builder()
                .body(order.getBody())
                .outTradeNo(order.getOutTradeNo())
                .appId(alipayConfig.getAppId())
                .userId(order.getUserId())
                .goodsNumber(order.getGoodsNumber())
                .totalAmount(order.getTotalAmount())
                .orderStatus(OrderStatus.PAY_WAITING.name())
                .subject(order.getSubject())
                .body(order.getBody())
                .payType(PayType.ALIPAY.name())
                .send(YN.NO.code)
                .build();
    }

    /**
     * 1、商户需要验证该通知数据中的out_trade_no是否为商户系统中创建的订单号，
     * 2、判断total_amount是否确实为该订单的实际金额（即商户订单创建时的金额），
     * 3、校验通知中的seller_id（或者seller_email)是否为out_trade_no这笔单据的对应的操作方（有的时候，一个商户可能有多个seller_id/seller_email），
     * 4、验证app_id是否为该商户本身。上述1、2、3、4有任何一个验证不通过，则表明本次通知是异常通知，务必忽略。
     * 在上述验证通过后商户必须根据支付宝不同类型的业务通知，正确的进行不同的业务处理，并且过滤重复的通知结果数据。
     * 在支付宝的业务通知中，只有交易通知状态为TRADE_SUCCESS或TRADE_FINISHED时，支付宝才会认定为买家付款成功。
     *
     * @throws AlipayApiException
     */
    @Transactional(rollbackFor = Exception.class)
    public PaymentAliPayNotifyRecordDO updateOrder(PaymentAliPayNotifyRecordDO notifyRecord) throws AlipayApiException {
        OrderRecordDO order = orderRecordService.findByOutTradeNo(notifyRecord.getOutTradeNo());
        if (Objects.isNull(order)) {
            throw new NullPointerException("没有查询到对应的订单！outTradeNo=" + notifyRecord.getOutTradeNo());
        }
        BigDecimal totalAmount = notifyRecord.getTotalAmount();
        if (totalAmount.compareTo(order.getTotalAmount()) != 0) {
            throw new IllegalArgumentException("订单金额不一致！outTradeNo=" + notifyRecord.getOutTradeNo());
        }
        //设置支付状态为已支付
        //是否首次变成已支付状态
        boolean firstChangeToPayed = notifyRecord.isFirstChangeToPayed();
        if (AlipayTradeStatus.TRADE_SUCCESS.toString().equals(notifyRecord.getTradeStatus())
                || AlipayTradeStatus.TRADE_FINISHED.toString().equals(notifyRecord.getTradeStatus())) {
            if (!OrderStatus.PAY_SUCCESS.name().equals(order.getOrderStatus())) {
                order.setOrderStatus(OrderStatus.PAY_SUCCESS.name());
                order.setPaymentTime(notifyRecord.getGmtPayment());
                order.setTradeNo(notifyRecord.getTradeNo());
                order.setPayEndTime(new Date());
            }
        }
        orderRecordService.save(order);
        save(notifyRecord);
        log.info("支付宝回调通知，修改本地订单成功！：notifyRecord = {}, 是否是首次变成支付成功 = {}", notifyRecord, firstChangeToPayed);
        notifyRecord.setFirstChangeToPayed(firstChangeToPayed);
        return notifyRecord;
    }

    /**
     * 保存回调通知
     *
     * @param info
     */
    public PaymentAliPayNotifyRecordDO save(PaymentAliPayNotifyRecordDO info) {
        if (info == null || StringUtils.isBlank(info.getTradeNo())) {
            log.error("对象为空！");
            return null;
        }
        return paymentAliPayNotifyRecordRepository.save(info);
    }

    /**
     * 订单查询接口
     *
     * @param outTradeNo outTradeNo 订单号
     * @return
     * @throws AlipayApiException
     */
    public AlipayTradeQueryResponse pullOrderStatus(String outTradeNo) {
        if (StringUtils.isBlank(outTradeNo)) {
            throw new BusinessException("商户订单号不能为空！");
        }

        AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();//创建API对应的request类
        AlipayTradeQueryModel model = new AlipayTradeQueryModel();
        model.setOutTradeNo(outTradeNo);
        request.setBizModel(model);
        AlipayTradeQueryResponse response = null;
        try {
            response = alipayClient.execute(request);
        } catch (AlipayApiException e) {
            log.error("支付宝订单查询状态报错");
            log.error("", e);
        }
        return response;
    }

    /**
     * 接收异步通知，并进行验证
     *
     * @param
     * @param params
     * @return
     * @throws AlipayApiException
     */
    @Transactional(rollbackFor = Exception.class)
    public PaymentAliPayNotifyRecordDO saveNotifyRecord(Map<String, String> params) throws AlipayApiException {
        PaymentAliPayNotifyRecordDO notifyRecord;
        JSONObject notifyJson = buildParamForJson(params);
        //验证并更新订单状态
        notifyRecord = JSONObject.parseObject(notifyJson.toJSONString(), PaymentAliPayNotifyRecordDO.class);
        notifyRecord = updateOrder(notifyRecord);
        notifyRecord.setSignVerified(true);
        return notifyRecord;
    }

    public boolean rsaCheckV1(HttpServletRequest request) {
        try {
            Map<String, String> params = new HashMap<>();
            Map<String, String[]> requestParams = request.getParameterMap();
            for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext(); ) {
                String name = (String) iter.next();
                String[] values = requestParams.get(name);
                String valueStr = "";
                for (int i = 0; i < values.length; i++) {
                    valueStr = (i == values.length - 1) ? valueStr + values[i] : valueStr + values[i] + ",";
                }
                params.put(name, valueStr);
            }

            return AlipaySignature.rsaCheckV1(params, alipayConfig.getAlipayPublicKey(), alipayConfig.getCharset(), alipayConfig.getSignType());
        } catch (AlipayApiException e) {
            log.debug("", e);
            return false;
        }
    }

    private JSONObject buildParamForJson(Map<String, String> stringParams) {
        Map<String, Object> params = new HashMap();

        for (Map.Entry<String, String> entry : stringParams.entrySet()) {
            String name = entry.getKey();
            String values = entry.getValue();

            params.put(name, values);
        }

        return new JSONObject(params);
    }


}